﻿using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Matrix;

public struct Matrix4X2 : IEquatable<Matrix4X2>
{
    private Vector2 _row0;

    public Vector2 Row0
    {
        get => _row0;
        set => _row0 = value;
    }

    private Vector2 _row1;

    public Vector2 Row1
    {
        get => _row1;
        set => _row1 = value;
    }

    private Vector2 _row2;

    public Vector2 Row2
    {
        get => _row2;
        set => _row2 = value;
    }

    private Vector2 _row3;

    public Vector2 Row3
    {
        get => _row3;
        set => _row3 = value;
    }

    public Matrix4X2(Vector2 row0, Vector2 row1, Vector2 row2, Vector2 row3)
    {
        _row0 = row0;
        _row1 = row1;
        _row2 = row2;
        _row3 = row3;
    }

    public Matrix4X2(float m00, float m01,
        float m10, float m11,
        float m20, float m21,
        float m30, float m31)
    {
        _row0 = new Vector2(m00, m01);
        _row1 = new Vector2(m10, m11);
        _row2 = new Vector2(m20, m21);
        _row3 = new Vector2(m30, m31);
    }

    public Vector4 Column0
    {
        get => new(M11, M21, M31, M41);
        set
        {
            M11 = value.X;
            M21 = value.Y;
            M31 = value.Z;
            M41 = value.W;
        }
    }

    public Vector4 Column1
    {
        get => new(M12, M22, M32, M42);
        set
        {
            M12 = value.X;
            M22 = value.Y;
            M32 = value.Z;
            M42 = value.W;
        }
    }

    public float M11
    {
        get => _row0.X;
        set => _row0.X = value;
    }

    public float M12
    {
        get => _row0.Y;
        set => _row0.Y = value;
    }

    public float M21
    {
        get => _row1.X;
        set => _row1.X = value;
    }

    public float M22
    {
        get => _row1.Y;
        set => _row1.Y = value;
    }

    public float M31
    {
        get => _row2.X;
        set => _row2.X = value;
    }

    public float M32
    {
        get => _row2.Y;
        set => _row2.Y = value;
    }

    public float M41
    {
        get => _row3.X;
        set => _row3.X = value;
    }

    public float M42
    {
        get => _row3.Y;
        set => _row3.Y = value;
    }

    public Vector2 Diagonal
    {
        get => new(M11, M22);
        set
        {
            M11 = value.X;
            M22 = value.Y;
        }
    }

    public float Trace => M11 + M22;

    public float this[int rowIndex, int columnIndex]
    {
        get
        {
            return rowIndex switch
            {
                0 => _row1[columnIndex],
                1 => _row1[columnIndex],
                2 => _row1[columnIndex],
                3 => _row2[columnIndex],
                _ => throw new IndexOutOfRangeException(
                    $"You tried to access this matrix at: ({rowIndex}, {columnIndex})")
            };
        }
        set
        {
            switch (rowIndex)
            {
                case 0:
                    _row0[columnIndex] = value;
                    break;
                case 1:
                    _row1[columnIndex] = value;
                    break;
                case 2:
                    _row2[columnIndex] = value;
                    break;
                case 3:
                    _row3[columnIndex] = value;
                    break;
                default:
                    throw new IndexOutOfRangeException(
                        $"You tried to access this matrix at: ({rowIndex}, {columnIndex})");
            }
        }
    }

    public static Matrix2X4 Transpose(Matrix4X2 matrix)
    {
        return new Matrix2X4
        (
            matrix.M11, matrix.M21, matrix.M31, matrix.M41,
            matrix.M12, matrix.M22, matrix.M32, matrix.M42
        );
    }

    public static Matrix4X2 Add(Matrix4X2 left, Matrix4X2 right)
    {
        return new Matrix4X2
        (
            left.M11 + right.M11, left.M12 + right.M12,
            left.M21 + right.M21, left.M22 + right.M22,
            left.M31 + right.M31, left.M32 + right.M32,
            left.M41 + right.M41, left.M42 + right.M42
        );
    }

    public static Matrix4X2 Subtract(Matrix4X2 left, Matrix4X2 right)
    {
        return new Matrix4X2
        (
            left.M11 - right.M11, left.M12 - right.M12,
            left.M21 - right.M21, left.M22 - right.M22,
            left.M31 - right.M31, left.M32 - right.M32,
            left.M41 - right.M41, left.M42 - right.M42
        );
    }

    public static Matrix4X2 Multiply(Matrix4X2 matrix, float scale)
    {
        return new Matrix4X2
        (
            matrix.M11 * scale, matrix.M12 * scale,
            matrix.M21 * scale, matrix.M22 * scale,
            matrix.M31 * scale, matrix.M32 * scale,
            matrix.M41 * scale, matrix.M42 * scale
        );
    }

    public static Matrix4X2 Multiply(Matrix4X2 left, Matrix2 right)
    {
        return new Matrix4X2
        (
            (left.M11 * right.M11) + (left.M12 * right.M21),
            (left.M11 * right.M12) + (left.M12 * right.M22),
            (left.M21 * right.M11) + (left.M22 * right.M21),
            (left.M21 * right.M12) + (left.M22 * right.M22),
            (left.M31 * right.M11) + (left.M32 * right.M21),
            (left.M31 * right.M12) + (left.M32 * right.M22),
            (left.M41 * right.M11) + (left.M42 * right.M21),
            (left.M41 * right.M12) + (left.M42 * right.M22)
        );
    }

    public static Matrix4X2 Multiply(Matrix4X2 left, Matrix4X2 right)
    {
        return new Matrix4X2
        (
            left.M11 * right.M11 + left.M12 * right.M21, left.M11 * right.M12 + left.M12 * right.M22,
            left.M21 * right.M11 + left.M22 * right.M21, left.M21 * right.M12 + left.M22 * right.M22,
            left.M31 * right.M11 + left.M32 * right.M21, left.M31 * right.M12 + left.M32 * right.M22,
            left.M41 * right.M11 + left.M42 * right.M21, left.M41 * right.M12 + left.M42 * right.M22
        );
    }

    public static Matrix4X3 Multiply(Matrix4X2 left, Matrix2X3 right)
    {
        return new Matrix4X3
        (
            left.M11 * right.M11 + left.M12 * right.M21,
            left.M11 * right.M12 + left.M12 * right.M22,
            left.M11 * right.M13 + left.M12 * right.M23,
            left.M21 * right.M11 + left.M22 * right.M21,
            left.M21 * right.M12 + left.M22 * right.M22,
            left.M21 * right.M13 + left.M22 * right.M23,
            left.M31 * right.M11 + left.M32 * right.M21,
            left.M31 * right.M12 + left.M32 * right.M22,
            left.M31 * right.M13 + left.M32 * right.M23,
            left.M41 * right.M11 + left.M42 * right.M21,
            left.M41 * right.M12 + left.M42 * right.M22,
            left.M41 * right.M13 + left.M42 * right.M23
        );
    }

    public static Matrix4 Multiply(Matrix4X2 left, Matrix2X4 right)
    {
        return new Matrix4
        (
            left.M11 * right.M11 + left.M12 * right.M21,
            left.M11 * right.M12 + left.M12 * right.M22,
            left.M11 * right.M13 + left.M12 * right.M23,
            left.M11 * right.M14 + left.M12 * right.M24,
            left.M21 * right.M11 + left.M22 * right.M21,
            left.M21 * right.M12 + left.M22 * right.M22,
            left.M21 * right.M13 + left.M22 * right.M23,
            left.M21 * right.M14 + left.M22 * right.M24,
            left.M31 * right.M11 + left.M32 * right.M21,
            left.M31 * right.M12 + left.M32 * right.M22,
            left.M31 * right.M13 + left.M32 * right.M23,
            left.M31 * right.M14 + left.M32 * right.M24,
            left.M41 * right.M11 + left.M42 * right.M21,
            left.M41 * right.M12 + left.M42 * right.M22,
            left.M41 * right.M13 + left.M42 * right.M23,
            left.M41 * right.M14 + left.M42 * right.M24
        );
    }

    public static Matrix4X2 CreateRotation(float angle)
    {
        var cos = MathHelper.Cos(angle);
        var sin = MathHelper.Sin(angle);

        return new Matrix4X2
        (
            cos, sin,
            -sin, cos,
            0, 0,
            0, 0
        );
    }

    public static Matrix4X2 CreateScale(float scale) => CreateScale(scale, scale);

    public static Matrix4X2 CreateScale(Vector2 scale) => CreateScale(scale.X, scale.Y);

    public static Matrix4X2 CreateScale(float scaleX, float scaleY)
    {
        return new Matrix4X2
        (
            scaleX, 0,
            0, scaleY,
            0, 0,
            0, 0
        );
    }

    public static Matrix4X2 operator +(Matrix4X2 left, Matrix4X2 right) => Add(left, right);
    public static Matrix4X2 operator -(Matrix4X2 left, Matrix4X2 right) => Subtract(left, right);
    public static Matrix4X2 operator *(float left, Matrix4X2 right) => Multiply(right, left);
    public static Matrix4X2 operator *(Matrix4X2 left, float right) => Multiply(left, right);
    public static Matrix4X2 operator *(Matrix4X2 left, Matrix2 right) => Multiply(left, right);
    public static Matrix4X3 operator *(Matrix4X2 left, Matrix2X3 right) => Multiply(left, right);
    public static Matrix4 operator *(Matrix4X2 left, Matrix2X4 right) => Multiply(left, right);

    public static bool operator ==(Matrix4X2 left, Matrix4X2 right) => left.Equals(right);
    public static bool operator !=(Matrix4X2 left, Matrix4X2 right) => !left.Equals(right);

    public override string ToString() => $"{_row0}\n{_row1}\n{_row2}\n{_row3}";

    public override int GetHashCode() => HashCode.Combine(_row0, _row1, _row2, _row3);

    public override bool Equals(object? obj) => obj is Matrix4X2 other && Equals(other);

    public bool Equals(Matrix4X2 other)
    {
        return _row0.Equals(other._row0) &&
               _row1.Equals(other._row1) &&
               _row2.Equals(other._row2) &&
               _row3.Equals(other._row3);
    }
}